﻿/*
 *  Copyright 2010 Kevin Price
 *  kpcrash@gmail.com
 *  The MIT License
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.IO;

namespace PFIFNet
{
    /// <summary>
    /// Processor for XML files formatted to PFIF 1.1/1.2 Specification
    /// </summary>
    public class PFIFDocument : XmlDocument
    {

        //private variables
        private string _version = "1.2";
        //private List<XmlElement> _People = new List<XmlElement>();
        private List<Person> _persons = new List<Person>();
        private XmlDocument xDoc;
        private XmlElement root;
        private XmlNamespaceManager nsMgr;
        public string Version
        {
            get { return _version; }
            set { _version = value.Trim(); }
        }
        public List<Person> Persons
        {
            get { return _persons; }
        }
        #region Internal Helper Functions

        #endregion
        public PFIFDocument()
        {
            xDoc = this;

            init();
        }

        public override void Load(string filename)
        {
            //uses reader when loading
            base.Load(filename);

        }
        public override void Load(Stream inStream)
        {
            base.Load(inStream);
        }
        public override void Load(TextReader txtReader)
        {
            base.Load(txtReader);
        }
        public override void Load(XmlReader reader)
        {
            base.Load(reader);
            TryParse();
        }
        public override void LoadXml(string xml)
        {
            base.LoadXml(xml);
            TryParse();
        }
        private void TryParse()
        {
            try
            {
                string nameTable = xDoc.DocumentElement.Attributes["xmlns:pfif"].Value;
                string pVersion = nameTable.Substring(nameTable.Length - 3, 3);
                this._version = pVersion;
                SetNS();
                XmlNodeList xPeople = xDoc.SelectNodes("//pfif:person", nsMgr);
                foreach (XmlNode node in xPeople)
                {
                    Person p = new Person(ref xDoc);
                    GetPersonValues(node, p);
                }
            }
            catch (Exception tpExc)
            {
                //Throw the exception back to the app consuming
                throw tpExc;
            }
        }
        private void GetPersonValues(XmlNode xPerson, Person pPerson)
        {
            //try to set all the properties of a person with
            // the values in the PFIF document
            try
            {
                string tmp = string.Empty;
                foreach (XmlNode xnode in xPerson.ChildNodes)
                {
                    //get the ones that are not notes
                    if (xnode.Name != "pfif:note")
                    {
                        if (xnode.Name == "pfif:home_zip")
                        {
                            tmp = "home_postal_code";
                        }
                        else
                        {
                            tmp = xnode.Name.Replace("pfif:", "");
                        }
                        System.Reflection.PropertyInfo pp = pPerson.GetType().GetProperty(tmp);
                        pp.SetValue(pPerson, xnode.InnerText, null);
                    }
                    else
                    {
                        //Add a note to that person
                        Note n = new Note(ref xDoc);
                        foreach (XmlNode nNode in xnode.ChildNodes)
                        {
                            tmp = nNode.Name.Replace("pfif:", "");
                            System.Reflection.PropertyInfo np = n.GetType().GetProperty(tmp);
                            np.SetValue(n, nNode.InnerText, null);
                        }

                        pPerson.PersonNotes.Add(n);
                    }
                }
                this._persons.Add(pPerson);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        private void init()
        {
            //Create the basic XML Document object and settings for pfif spec
            XmlDeclaration dec = xDoc.CreateXmlDeclaration("1.0", "utf-8", null);
            xDoc.AppendChild(dec);
            SetNS();
            root = xDoc.CreateElement("pfif", "pfif", "http://zesty.ca/pfif/" + this.Version);
            xDoc.AppendChild(root);
        }
        private void SetNS()
        {
            nsMgr = new XmlNamespaceManager(xDoc.NameTable);
            nsMgr.RemoveNamespace("pfif", "http://zesty.ca/pfif/1.2");
            nsMgr.AddNamespace("pfif", "http://zesty.ca/pfif/" + this.Version);
        }
        /// <summary>
        /// Will convert any valid DateTime object to GMT string
        /// </summary>
        /// <param name="dToConvert"></param>
        /// <returns></returns>
        public string ConvertDate(DateTime dToConvert)
        {
            try
            {
                //d.Year.ToString() + '-' + d.Month.ToString() + '-' + d.Day.ToString() + 'T' + d.Hour.ToString() + ':' + d.Minute.ToString() + ':' + d.Second.ToString() + 'Z';
                string m = dToConvert.Month.ToString();
                if (m.Length == 1) m = "0" + m;
                string d = dToConvert.Day.ToString();
                if (d.Length == 1) d = "0" + d;
                string h = dToConvert.Hour.ToString();
                if (h.Length == 1) h = "0" + h;
                string mm = dToConvert.Minute.ToString();
                if (mm.Length == 1) mm = "0" + mm;
                string sec = dToConvert.Second.ToString();
                if (sec.Length == 1) sec = "0" + sec;
                return dToConvert.Year.ToString() + "-" + m + "-" + d + "T" + h + ":" + mm + ":" + sec + "Z";
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        //private XmlElement Note;
        //private List<XmlElement> Notes;
        #region Person class
        public class Person
        {


            //public properties


            #region Private Declarations

            //Person Tracking Info
            private string _person_record_id = string.Empty;
            private string _entry_date = string.Empty;
            private string _author_name = string.Empty;
            private string _author_email = string.Empty;
            private string _author_phone = string.Empty;
            private string _source_name = string.Empty;
            private string _source_date = string.Empty;
            private string _source_url = string.Empty;

            //Person Static Info
            private string _first_name = string.Empty;
            private string _last_name = string.Empty;
            private string _home_city = string.Empty;
            private string _home_state = string.Empty;
            private string _home_neighborhood = string.Empty;
            private string _home_street = string.Empty;
            private string _home_country = string.Empty;
            private string _home_postal_code = string.Empty;
            private string _sex = string.Empty;
            private string _age = string.Empty;
            private string _date_of_birth = string.Empty;
            private string _photo_url = string.Empty;
            private string _other = string.Empty;
            private List<XmlElement> _notes = new List<XmlElement>();
            private List<XmlElement> _people = new List<XmlElement>();
            private List<Note> _personnotes = new List<Note>();
            private XmlDocument pxDoc;
            private XmlElement rootEl;
            #endregion
            #region Public Properties

            public string person_record_id
            {
                get { return _person_record_id; }
                set { _person_record_id = value.Trim(); }
            }
            public string entry_date
            {
                get { return _entry_date; }
                set { _entry_date = value.Trim(); }
            }
            public string author_name
            {
                get { return _author_name; }
                set { _author_name = value.Trim(); }
            }
            public string author_email
            {
                get { return _author_email; }
                set { _author_email = value.Trim(); }
            }
            public string author_phone
            {
                get { return _author_phone; }
                set { _author_phone = value.Trim(); }
            }
            public string source_name
            {
                get { return _source_name; }
                set { _source_name = value.Trim(); }
            }
            public string source_date
            {
                get { return _source_date; }
                set { _source_date = value.Trim(); }
            }
            public string source_url
            {
                get { return _source_url; }
                set { _source_url = value.Trim(); }
            }
            public string first_name
            {
                get { return _first_name; }
                set { _first_name = value.Trim(); }
            }
            public string last_name
            {
                get { return _last_name; }
                set { _last_name = value.Trim(); }
            }
            public string sex
            {
                get { return _sex; }
                set { _sex = value; }
            }
            public string date_of_birth
            {
                get { return _date_of_birth; }
                set { _date_of_birth = value; }
            }
            public string age
            {
                get { return _age; }
                set { _age = value.Trim(); }
            }
            public string home_street
            {
                get { return _home_street; }
                set { _home_street = value.Trim(); }
            }
            public string home_neighborhood
            {
                get { return _home_neighborhood; }
                set { _home_neighborhood = value; }
            }
            public string home_city
            {
                get { return _home_city; }
                set { _home_city = value.Trim(); }
            }
            public string home_state
            {
                get { return _home_state; }
                set { _home_state = value.Trim(); }
            }
            public string home_country
            {
                get { return _home_country; }
                set { _home_country = value.Trim(); }
            }
            public string home_postal_code
            {
                get { return _home_postal_code; }
                set { _home_postal_code = value; }
            }
            public string photo_url
            {
                get { return _photo_url; }
                set { _photo_url = value.Trim(); }
            }
            public string other
            {
                get { return _other; }
                set { _other = value.Trim(); }
            }
            public List<XmlElement> Notes
            {
                get { return _notes; }
            }
            public List<XmlElement> People
            {
                get { return _people; }
            }
            public List<Note> PersonNotes
            {
                get { return _personnotes; }
            }
            #endregion
            /// <summary>
            /// Creates person record to add to document.
            /// Expects existing PFIFDocument as input
            /// </summary>
            /// <param name="pDoc"></param>
            public Person(ref PFIFDocument pDoc)
            {
                this.pxDoc = pDoc;
                this.rootEl = pDoc.DocumentElement;
            }
            public Person(ref XmlDocument xDoc)
            {
                this.pxDoc = xDoc;
                this.rootEl = xDoc.DocumentElement;
            }
            public void AddPerson()
            {
                string nsURI = rootEl.NamespaceURI;
                XmlElement personEl = pxDoc.CreateElement("pfif", "person", nsURI);
                //Note n = new Note(ref personEl);
                //write all the property values to the record
                foreach (System.Reflection.PropertyInfo p in this.GetType().GetProperties())
                {
                    if (p.PropertyType == typeof(string))
                    {
                        XmlElement x = pxDoc.CreateElement("pfif", p.Name, nsURI);
                        x.InnerText = p.GetValue(this, null).ToString();
                        personEl.AppendChild(x);
                    }
                }

                //Add the person to the main document
                pxDoc.DocumentElement.AppendChild(personEl);

                _people.Add(personEl);
            }
            //Add a note to a person record and
            //add it to the Notes collection
            public void AddNote(XmlElement personElement)
            {
                Note n = new Note(ref personElement);
                XmlElement nt = n.AddNote();
                //personElement.AppendChild(nt);
                _notes.Add(nt);
            }
            public XmlElement AddNote(ref Note cn, XmlElement personElement)
            {
                string nsURI = rootEl.NamespaceURI;
                XmlElement noteEL = pxDoc.CreateElement("pfif", "note", nsURI);
                //write all the property values to the record
                foreach (System.Reflection.PropertyInfo p in cn.GetType().GetProperties())
                {
                    if (p.PropertyType == typeof(string))
                    {
                        XmlElement x = pxDoc.CreateElement("pfif", p.Name, nsURI);
                        x.InnerText = p.GetValue(cn, null).ToString();
                        noteEL.AppendChild(x);
                    }
                }
                personElement.AppendChild(noteEL);
                _notes.Add(noteEL);
                return noteEL;
            }

        }
        #endregion
        #region Note class
        public class Note
        {
            #region Private Declarations
            private string _note_record_id = string.Empty;
            private string _entry_date = string.Empty;
            private string _author_name = string.Empty;
            private string _author_email = string.Empty;
            private string _author_phone = string.Empty;
            private string _found = string.Empty;
            private string _source_date = string.Empty;
            private string _email_of_found_person = string.Empty;
            private string _phone_of_found_person = string.Empty;
            private string _last_known_location = string.Empty;
            private string _text = string.Empty;
            private string _person_record_id = string.Empty;
            private string _linked_person_record_id = string.Empty;
            private string _status = string.Empty;
            private XmlElement rootEl;
            #endregion
            #region Public Properties
            public string note_record_id
            {
                get { return _note_record_id; }
                set { _note_record_id = value.Trim(); }
            }
            public string person_record_id
            {
                get { return _person_record_id; }
                set { _person_record_id = value.Trim(); }
            }
            public string linked_person_record_id
            {
                get { return _linked_person_record_id; }
                set { _linked_person_record_id = value.Trim(); }
            }
            public string entry_date
            {
                get { return _entry_date; }
                set { _entry_date = value.Trim(); }
            }
            public string author_name
            {
                get { return _author_name; }
                set { _author_name = value.Trim(); }
            }
            public string author_email
            {
                get { return _author_email; }
                set { _author_email = value.Trim(); }
            }
            public string author_phone
            {
                get { return _author_phone; }
                set { _author_phone = value.Trim(); }
            }
            public string source_date
            {
                get { return _source_date; }
                set { _source_date = value.Trim(); }
            }
            public string status
            {
                get { return _status; }
                set { _status = value.Trim(); }
            }
            public string found
            {
                get { return _found; }
                set { _found = value.Trim(); }
            }
            public string email_of_found_person
            {
                get { return _email_of_found_person; }
                set { _email_of_found_person = value.Trim(); }
            }
            public string phone_of_found_person
            {
                get { return _phone_of_found_person; }
                set { _phone_of_found_person = value.Trim(); }
            }
            public string last_known_location
            {
                get { return _last_known_location; }
                set { _last_known_location = value; }
            }
            public string text
            {
                get { return _text; }
                set { _text = value.Trim(); }
            }
            #endregion
            /// <summary>
            /// Creates a new instance of the Note class.
            /// Requires a ref to the pfif:person element
            /// </summary>
            /// <param name="nElem"></param>
            public Note(ref XmlElement nElem)
            {
                this.rootEl = nElem;
            }
            /// <summary>
            /// Creates a new instance of the Note Class.
            /// Must use Person.AddNote() when instantiating
            /// this way.
            /// </summary>
            public Note()
            {

            }
            /// <summary>
            /// Creates new instance of Note class.
            /// Use this when you want to create a note
            ///  that is not connected to a person record
            /// </summary>
            /// <param name="doc"></param>
            public Note(ref PFIFDocument doc)
            {
                this.rootEl = doc.DocumentElement;
            }
            public Note(ref XmlDocument xDoc)
            {
                this.rootEl = xDoc.DocumentElement;
            }
            public XmlElement AddNote()
            {
                string nsURI = rootEl.NamespaceURI;
                XmlDocument tmpDoc = rootEl.OwnerDocument;
                XmlElement noteEL = tmpDoc.CreateElement("pfif", "note", nsURI);
                //write all the property values to the record
                foreach (System.Reflection.PropertyInfo p in this.GetType().GetProperties())
                {
                    if (p.PropertyType == typeof(string))
                    {
                        XmlElement x = tmpDoc.CreateElement("pfif", p.Name, nsURI);
                        x.InnerText = p.GetValue(this, null).ToString();
                        noteEL.AppendChild(x);
                    }
                }
                rootEl.AppendChild(noteEL);
                return noteEL;
            }


        }
        #endregion

    }
}
